package cn.hbads.version.domain.version.aggregate;

import cn.hbads.support.date.DateUtil;
import cn.hbads.support.error.ErrorType;
import cn.hbads.support.structure.result.OpResult;
import cn.hbads.version.domain.version.model.ChangePoint;
import cn.hbads.version.domain.version.model.DataVersionModifyModel;
import cn.hbads.version.domain.version.model.VersionType;
import cn.hbads.version.error.VersionErrorType;
import cn.hutool.core.collection.CollUtil;
import lombok.Getter;

import java.time.LocalDate;
import java.util.*;
import java.util.function.Consumer;

/**
 * <b> 数据版本链的默认抽象实现-TreeMap</b>
 *
 * @author : yvvb
 * @date : 2025/5/5
 */
public abstract class AbstractDataVersionChain<T, E extends DataVersion<T>> implements DataVersionChain<T, E> {

    @Getter
    private final T dataId;
    private final TreeMap<LocalDate, E> versionDateTreeMap = new TreeMap<>();
    private final TreeMap<LocalDate, E> expirationDateTreeMap = new TreeMap<>();
    List<E> removeVersions = new ArrayList<>();

    protected AbstractDataVersionChain(T dataId, List<E> elements) {
        this.dataId = dataId;
        if (CollUtil.isNotEmpty(elements)) {
            elements.forEach(e -> {
                this.versionDateTreeMap.putIfAbsent(e.getVersionDate(), e);
                this.expirationDateTreeMap.putIfAbsent(e.getExpirationDate(), e);
            });
        }
    }

    protected void addDataVersion(E element) {
        element.setChanged(true);
        this.versionDateTreeMap.put(element.getVersionDate(), element);
        this.expirationDateTreeMap.put(element.getExpirationDate(), element);
    }

    protected void removeDataVersion(E element) {
        element.setRemoved(true);
        this.versionDateTreeMap.remove(element.getVersionDate());
        this.expirationDateTreeMap.remove(element.getExpirationDate());
        this.removeVersions.add(element);
    }

    protected void refreshVersionDateElement(E element) {
        element.setChanged(true);
        this.versionDateTreeMap.remove(element.getVersionDate());
        this.versionDateTreeMap.put(element.getVersionDate(), element);
    }

    protected void refreshExpirationDateElement(E element) {
        element.setChanged(true);
        this.expirationDateTreeMap.remove(element.getExpirationDate());
        this.expirationDateTreeMap.put(element.getExpirationDate(), element);
    }

    protected void clearAllElements() {
        this.versionDateTreeMap.forEach((date, element) -> {
            element.setChanged(true);
            this.removeVersions.add(element);
        });
        this.versionDateTreeMap.clear();
        this.expirationDateTreeMap.clear();
    }


    @Override
    public LocalDate getGenesisDate() {
        return CollUtil.isNotEmpty(this.versionDateTreeMap) ? this.versionDateTreeMap.firstKey() : null;
    }

    @Override
    public Optional<LocalDate> getDeletedDate() {
        return Optional.ofNullable(this.versionDateTreeMap.lastEntry())
                .map(entry -> {
                    if (Objects.nonNull(entry.getValue()) && entry.getValue().isDeleted()) {
                        return entry.getKey();
                    } else {
                        return null;
                    }
                });
    }

    @Override
    public Optional<E> findEffectiveVersion(LocalDate queryDate) {
        return Optional.ofNullable(this.versionDateTreeMap.floorEntry(queryDate))
                .map(Map.Entry::getValue);
    }

    @Override
    public Optional<E> findVersionByVersionDateEq(LocalDate versionDate) {
        return Optional.ofNullable(this.versionDateTreeMap.get(versionDate));
    }

    @Override
    public Optional<E> findVersionByExpirationDateEq(LocalDate expirationDate) {
        return Optional.ofNullable(this.expirationDateTreeMap.get(expirationDate));
    }

    @Override
    public Optional<E> findPrecedingVersion(E dataVersion) {
        return Optional.ofNullable(this.versionDateTreeMap.lowerEntry(dataVersion.getVersionDate())).map(Map.Entry::getValue);
    }

    @Override
    public Optional<E> findFollowingVersion(E dataVersion) {
        return Optional.ofNullable(this.versionDateTreeMap.higherEntry(dataVersion.getVersionDate())).map(Map.Entry::getValue);
    }

    @Override
    public Collection<E> getAllVersions() {
        return this.versionDateTreeMap.values();
    }

    @Override
    public Collection<E> getChangedVersions() {
        final var elements = this.versionDateTreeMap.values();
        if (CollUtil.isEmpty(elements)) {
            return List.of();
        }
        return elements.stream().filter(DataVersion::isChanged).toList();
    }

    @Override
    public Collection<E> getRemovedVersions() {
        return this.removeVersions;
    }

    @Override
    public Collection<E> getBehindVersions(E current) {
        return this.versionDateTreeMap.tailMap(current.getVersionDate(), false).values();
    }

    @Override
    public Collection<E> getPrecedingVersions(E current) {
        return this.versionDateTreeMap.headMap(current.getVersionDate(), false).values();
    }

    @Override
    public Collection<E> getVersionsByVersionDateBetween(LocalDate startDate, LocalDate endDate) {
        return this.versionDateTreeMap.subMap(startDate, true, endDate, true).values();
    }

    @Override
    public Collection<E> getVersionsByExpirationDateBetween(LocalDate startDate, LocalDate endDate) {
        return this.expirationDateTreeMap.subMap(startDate, true, endDate, true).values();
    }

    @Override
    public Collection<E> getVersionsByRangeCross(LocalDate startDate, LocalDate endDate) {
        if (CollUtil.isEmpty(this.versionDateTreeMap.values())) {
            return List.of();
        }
        return this.versionDateTreeMap.values().stream().filter(e -> DateUtil.noAfter(e.getVersionDate(), endDate)
                && DateUtil.noBefore(e.getExpirationDate(), startDate)).toList();
    }

    @Override
    public OpResult<ErrorType> modify(LocalDate modifyDate, DataVersionModifyModel modifyModel) {
        Optional<E> effectiveDataVersionOpt = findEffectiveVersion(modifyDate);
        if (effectiveDataVersionOpt.isEmpty()) {
            return OpResult.fail(VersionErrorType.DATA_VERSION_NOT_EXIST);
        }
        DataVersion<T> effectiveDataVersion = effectiveDataVersionOpt.get();
        LocalDate effectiveVersionDate = effectiveDataVersion.getVersionDate();
        // 是否同一天，需要进行版本合并
        if (DateUtil.isSameDay(modifyDate, effectiveVersionDate)) {
            // 合并变更
            effectiveDataVersion.modifyVersion(modifyModel);
            if (VersionType.checkTransferValid(effectiveDataVersion.getVersionType(), VersionType.MODIFY)) {
                effectiveDataVersion.setVersionType(VersionType.MODIFY);
            }
        } else {
            E postModifiedVersion = effectiveDataVersion.splitVersion(modifyDate);
            postModifiedVersion.modifyVersion(modifyModel);
            addDataVersion(postModifiedVersion);
        }
        return OpResult.success();
    }

    @Override
    public OpResult<ErrorType> delete(LocalDate deleteDate) {
        // 数据是否存在
        Optional<E> effectiveDataVersionOpt = findEffectiveVersion(deleteDate);
        if (effectiveDataVersionOpt.isEmpty()) {
            return OpResult.fail(VersionErrorType.DATA_VERSION_NOT_EXIST);
        }
        // 数据版本是否已被删除
        E effectiveDataVersion = effectiveDataVersionOpt.get();
        if (effectiveDataVersion.isDeleted()) {
            return OpResult.fail(VersionErrorType.DATA_VERSION_HAD_BEEN_DELETED);
        }
        // 删除创建版本特殊处理
        if (DateUtil.isSameDay(deleteDate, this.getGenesisDate())) {
            this.clearAllElements();
            return OpResult.success();
        }
        // 删除数据版本是否需要合并
        LocalDate effectiveVersionDate = effectiveDataVersion.getVersionDate();
        Collection<E> behindDataVersions = getBehindVersions(effectiveDataVersion);
        if (DateUtil.isSameDay(deleteDate, effectiveVersionDate)) {
            // 合并
            effectiveDataVersion.setDeleted(true);
            if (VersionType.checkTransferValid(effectiveDataVersion.getVersionType(), VersionType.DELETE)) {
                effectiveDataVersion.setVersionType(VersionType.DELETE);
            }
        } else {
            // 分裂
            final E deletedVersion = effectiveDataVersion.splitVersion(deleteDate);
            deletedVersion.setDeleted(true);
            deletedVersion.setVersionType(VersionType.DELETE);
            addDataVersion(deletedVersion);
        }
        effectiveDataVersion.setChanged(true);
        // 移除
        for (E behindDataVersion : behindDataVersions) {
            final OpResult<ErrorType> result = this.remove(behindDataVersion);
            if (!result.isSuccess()) {
                return result;
            }
        }
        return OpResult.success();
    }

    @Override
    public OpResult<ErrorType> move(E originVersion, LocalDate targetDate) {
        // 检测是否移动到当天之前（一般业务上没有意义不允许）
        if (targetDate.isBefore(LocalDate.now())) {
            return OpResult.fail(VersionErrorType.MOVE_TARGET_DATE_MUST_NO_BEFORE_TODAY);
        }
        // 检测版本移动是否跨越已存在的版本
        final var followingVersionOpt = findFollowingVersion(originVersion);
        if (followingVersionOpt.isPresent()) {
            final var followingVersion = followingVersionOpt.get();
            if (DateUtil.noBefore(targetDate, followingVersion.getVersionDate())) {
                return OpResult.fail(VersionErrorType.MOVE_DATE_MUST_NO_CROSS_EXISTS_VERSION);
            }
        }
        final var precedingVersionOpt = findPrecedingVersion(originVersion);
        if (precedingVersionOpt.isPresent()) {
            final var precedingVersion = precedingVersionOpt.get();
            if (DateUtil.noAfter(targetDate, precedingVersion.getVersionDate())) {
                return OpResult.fail(VersionErrorType.MOVE_DATE_MUST_NO_CROSS_EXISTS_VERSION);
            }

            precedingVersion.setExpirationDate(targetDate.minusDays(1));
            precedingVersion.setChanged(true);
        }
        final E movedVersion = originVersion.moveVersion(targetDate);
        addDataVersion(movedVersion);
        removeDataVersion(originVersion);
        return OpResult.success();
    }

    @Override
    public OpResult<ErrorType> remove(E removedVersion) {
        final Optional<E> precedingVersionOpt = findPrecedingVersion(removedVersion);
        if (precedingVersionOpt.isEmpty()) {
            return OpResult.success();
        }
        final var precedingVersion = precedingVersionOpt.get();
        removeDataVersion(removedVersion);
        precedingVersion.setExpirationDate(removedVersion.getVersionDate());
        precedingVersion.setChanged(true);
        return OpResult.success();
    }


    @Override
    public OpResult<ErrorType> duelChangePoint(ChangePoint<T, E> changePoint) {
        final LocalDate changeDate = changePoint.getChangeDate();

        final Optional<E> targetDataVersionOpt = findEffectiveVersion(changeDate);
        if (targetDataVersionOpt.isEmpty()) {
            return OpResult.fail(VersionErrorType.DATA_VERSION_NOT_EXIST);
        }
        final E targetDataVersion = targetDataVersionOpt.get();
        final Consumer<E> changeConsumer = changePoint.getChangeConsumer();
        if ((DateUtil.isSameDay(targetDataVersion.getVersionDate(), changeDate))) {
            changeConsumer.accept(targetDataVersion);
            if (VersionType.checkTransferValid(targetDataVersion.getVersionType(), changePoint.getChangeVersionType())) {
                targetDataVersion.setVersionType(changePoint.getChangeVersionType());
            }
        } else {
            final E changedDataVersion = targetDataVersion.splitVersion(changeDate);
            changeConsumer.accept(changedDataVersion);
            changedDataVersion.setVersionType(changePoint.getChangeVersionType());
            addDataVersion(changedDataVersion);
        }
        targetDataVersion.setChanged(true);
        return OpResult.success();
    }
}
